﻿//
// $HeadURL:  $
// $Revision:  $
// $Author:  $     $Date:  $
//
// Copyright © 2009-2010 Dean Harding, all rights reserved.
//

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Reflection;

namespace Codeka.Memcache.Tests.ServerBinary
{
    /// <summary>
    /// This helper class launches the server binary listening on a specific
    /// port, and later kills is when you're done.
    /// </summary>
    static class ServerBinaryHelper
    {
        /// <summary>
        /// Launches a copy of memcached.exe listening on the specified port
        /// and returns a value that, when disposed, will kill the process.
        /// </summary>
        /// <param name="port">The port you want this instance of memcached.exe to
        /// listen on.</param>
        /// <returns>An <see cref="IDisposable"/> that'll kill the process when
        /// you dispose it.</returns>
        public static IDisposable Launch(int port)
        {
            // first, create a temporary directory
            var basePath = Path.GetTempPath();
            var directoryName = Path.Combine(basePath, "Codeka", "Memcache", Guid.NewGuid().ToString().Replace("-", ""));
            Directory.CreateDirectory(directoryName);

            // extract the exe and dlls to the new directory
            ExtractFile(directoryName, "libgcc_s_sjlj-1.dll");
            ExtractFile(directoryName, "memcached.exe");
            ExtractFile(directoryName, "pthreadGC2.dll");

            // set up the parameters for the new process
            var cmdline = string.Format("-l 127.0.0.1 -p {0} -vv", port);
            var psi = new ProcessStartInfo(Path.Combine(directoryName, "memcached.exe"), cmdline);
            psi.CreateNoWindow = true;
            psi.UseShellExecute = false;
            psi.WindowStyle = ProcessWindowStyle.Hidden;
            psi.RedirectStandardError = true;
            psi.RedirectStandardOutput = true;

            // launch the process
            var proc = Process.Start(psi);
            proc.ErrorDataReceived += proc_OutputDataReceived;
            proc.OutputDataReceived += proc_OutputDataReceived;
            proc.BeginErrorReadLine();
            proc.BeginOutputReadLine();
            
            // unfortunately, there's not really any way to know when it's ready
            // so we just got to wait a bit...
            Thread.Sleep(250);

            // return the LaunchDisposable that'll kill the process and delete the files
            return new LaunchDisposable(proc, directoryName);
        }

        /// <summary>
        /// This is called when we receive data from a running memcached.exe process, we'll
        /// just copy it to our own output.
        /// </summary>
        static void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            Console.WriteLine("Memcached: {0}", e.Data);
        }

        /// <summary>
        /// Extracts the file with the given <c>name</c> to the given directory.
        /// </summary>
        private static void ExtractFile(string directoryName, string name)
        {
            using (var ins = Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(ServerBinaryHelper), name))
            using (var outs = new FileStream(Path.Combine(directoryName, name), FileMode.Create))
            {
                ins.CopyTo(outs);
            }
        }

        /// <summary>
        /// This is the class we return from <b>Launch</b>. It kills the process and
        /// deletes the directory it was extracted to.
        /// </summary>
        class LaunchDisposable : IDisposable
        {
            private Process process;
            private string directoryName;

            public LaunchDisposable(Process proc, string directoryName)
            {
                this.process = proc;
                this.directoryName = directoryName;
            }

            public void Dispose()
            {
                process.Kill();

                for (int i = 0; i < 10; i++)
                {
                    try
                    {
                        Directory.Delete(directoryName, true);
                        break;
                    }
                    catch (Exception)
                    {
                        // we might get an exception if the process hasn't finished
                        // cleaning up yet
                        Thread.Sleep(100);
                    }
                }
            }
        }
    }
}
